package mirabilis.org.simplices;

import java.io.ObjectInputStream.GetField;
import java.util.Iterator;
import java.util.TreeSet;

import org.jblas.DoubleMatrix;
import org.jblas.Solve;

import random.CommonState;

import mirabilis.org.IEvaluator;
import mirabilis.org.config.Configuration;
import mirabilis.org.config.LoadProperties;
import mirabilis.org.termination.Termination;
import mirabilis.org.util.Bounds;
import mirabilis.org.util.Gradients;
import mirabilis.org.util.Initialization;
import mirabilis.samples.cec2014.Cec2014;

public class Simplex implements Cloneable{
	
	private TreeSet<Vertex> _simplex = new TreeSet<Vertex>();
	
	//Vertex[] _simplex = new Vertex[Configuration.N+1];
	
	private IEvaluator _evaluator;
	
	/**
	 * Initialize the simplex
	 * @param evaluator
	 * @param random if true the simplex is initialize randomly, if false, the simplex is initialize in orthogonal directions to an initial point
	 */
	public Simplex(IEvaluator evaluator, boolean random) {
		
		_evaluator = evaluator;
		
		if(random){
			for(int i=0;i<Configuration.N+1;i++){
				addVertex(randomCoordinate());
			}
		}else{
			Vertex[] nsimplex = Initialization.init_simplex(evaluator);
			for(int i=0;i<Configuration.N+1;i++)
				addVertex(nsimplex[i]);
		}
				
	}

	
	//Use in clone()
	public Simplex(){
		
	}
	
	public Simplex(IEvaluator evaluator) {
		
		_evaluator = evaluator;
		
		//Coordinates for a regular simplex in N dimensions where
		//the distance from barycenter to every vertex is one
		double[] x = Initialization.simplex_coordinates(Configuration.N);
		
		//Volume of the hypercube defined by the lower and upper bounds in each dimension 
		double hypercubeVolume = Initialization.volume_constrained_hypercube(Configuration.N, Configuration.bounds);
		
		// 'o' is the scalar to multiply the regular simplex by.
		double o = Initialization.magnitude_of_scale(Configuration.N, hypercubeVolume/2.0);
		
		//TODO: next line is for debugging: erase without asking
		o /= (Configuration.N*1.0);
		
		//Coordinates for the initial simplex
		// It is scaled by an 'o' factor as to let the simplex occupy 1/2 of the hypervolume    
		double[] sc = Initialization.scale_regular_simplex(o, x);
		
		// A little hack to go:
		//    -from a single long vector containing all vertices
		//    -to a matrix with N+1 vertices of dimension N
		double[][] nsimplex = new double[Configuration.N+1][Configuration.N];
		int n=-1;
		for(int i=0; i<sc.length;i++){
			if(i%Configuration.N==0){
				n++;
			}
			nsimplex[n][i%Configuration.N] = sc[i];
		}
		n++; // be aware that n should be equal to N+1
		
		// We add the vertex
		for(int i=0;i<n;i++)
			addVertex(nsimplex[i]);
		
	}
	
	
	
	private static double[] randomCoordinate(){
		double[] coor = new double[Configuration.N];
		for(int i=0;i<Configuration.N;i++){
			double lbound = Configuration.bounds[i*2];
			double ubound = Configuration.bounds[(i*2)+1];
			coor[i] = CommonState.r.nextDouble() * (ubound-lbound) + lbound;
			//System.err.print(coor[i]+",");
		}
		//System.err.println();
		return coor;
	}
	
	public IEvaluator getEvaluator(){
		return _evaluator;
	}
	

	
	//Evaluation is done at this step
	// it returns a pointer to the vertex
	public Vertex addVertex(double [] coordinates){
		
		//Assign coordinates to a new vertex
		Vertex v = new Vertex(new Coordinates(coordinates));
		
		//Evaluate the coordinates within the targeted bounds
		//i.e. Evaluation after translating coordinates into toroidal coordinates
		//double fitness = _evaluator.evaluate(v.getCoordinates().getCoordinatesInTorusAsDouble());
		double fitness =  Bounds.fitnessWithWallAtTheBounds(v.getCoordinates().getCoordinatesAsDouble(), this);
		v.setFitness(fitness);
		
		//The increment of evaluations is now done at Bounds.fitnessWithInterpolationAtTheBounds
		//Increments the evaluation by one
		//Termination.increment_evaluation();
		
		while(!_simplex.add(v)){
			try {
				v.setFitness(v.getFitness()+0.000001);
			} catch (FitnessNotEvaluatedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		
		
		return v;
	}
	
		public void addVertex(Vertex vertex){
			while(!_simplex.add(vertex)){
				try {
					vertex.setFitness(vertex.getFitness()+0.000001);
				} catch (FitnessNotEvaluatedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}
	
	public Vertex addVertexWithoutFitness(double[] coordinates){
		Vertex v = new Vertex(new Coordinates(coordinates));
		v.setFitness(CommonState.r.nextDouble());
		while(!_simplex.add(v)){
			v.setFitness(CommonState.r.nextDouble());
		}
		
		return v;
	}
	
	
	
	public int size(){
		return _simplex.size();
	}
	//Return true if the vertex is equal to the first element in the simplex
	public boolean isHead(Vertex v){
//		if (getBest().equals(v))
//			System.out.println("T");
//		else
//			System.out.println("F");
		return getBest().equals(v);
	}
	
	//Return true if the vertex is not the worst/last element in the simplex (i.e. not the arrow)
		public boolean isBow(Vertex v){
//			if (getBest().equals(v))
//				System.out.println("T");
//			else
//				System.out.println("F");
			return !getArrow().equals(v);
		}
	
	public boolean removeVertex(Vertex v){
		return _simplex.remove(v);
	}

	public Vertex getBest(){
		return _simplex.first();
	}
	
	public Vertex getWorseThan(Vertex v){
		return _simplex.higher(v);
	}
	
	//This operation removes the worst element
	public Vertex pollArrow(){
		return _simplex.pollLast();
	}
	
	//This operation get the worst element without removing
		public Vertex getArrow(){
			return _simplex.last();
		}
	
	public Iterator<Vertex> iterator(){
		return _simplex.iterator();
	}
	
	public String asString(){
		String simplex="";
		for(Iterator<Vertex> it = _simplex.iterator();it.hasNext();){
			Vertex v = it.next();
			simplex +=v.asString()+" ";
		}
		return simplex;
	}
	
	public String asStringOfTorus(){
		String simplex="";
		for(Iterator<Vertex> it = _simplex.iterator();it.hasNext();){
			Vertex v = it.next();
			simplex +=v.asStringOfTorus()+" ";
		}
		return simplex;
	}
	
	public boolean isEmpty(){
		return _simplex.isEmpty();
	}
	
	public Object clone() {
		// TODO Auto-generated method stub
		
		Simplex theclone = new Simplex();
		
		theclone._evaluator = this._evaluator;
		
		Iterator<Vertex> it = _simplex.iterator();
		
		for(;it.hasNext();){
			Vertex v = it.next();
			theclone._simplex.add((Vertex)v.clone());
		}
		
		return theclone;
	}
	
	
	public static void main(String[] args) {
		//Setting up properties
		LoadProperties lp = new LoadProperties(args);
		Configuration.setConfiguration(lp);	
		Configuration.F=2;
		Configuration.minimization = true;
		Configuration.N=2;
		Configuration.upper_bound= 100;
		Configuration.lower_bound= -100;
		Configuration.setBounds();
			
		
		//Testing the structure of the first simplex
		Cec2014 bench = new Cec2014();

		
		Simplex simplex = new Simplex(bench);
		
		System.out.println("----------Simplex----------");
		for (Iterator<Vertex> it = simplex.iterator(); it.hasNext();){
			Vertex y_n = it.next();
			try {
				System.out.println(y_n.getCoordinates().asString()+" "+y_n.getFitness());
			} catch (FitnessNotEvaluatedException e) {	e.printStackTrace();}
		}
		
		double[] gradients = Gradients.gradient(simplex);
		
		System.out.println("----------Hypervolume----------");
		double volumeSimplex = Initialization.simplex_volume(Configuration.N, Gradients.hackToGoArrayContainingASimplex(simplex));
		
		System.out.println(""+volumeSimplex);
		
		System.out.println("----------Gradients----------");
		
		for (int i=0;i<gradients.length;i++){
			System.out.print(gradients[i]+" ");
		}
		
		
//		while(!simplex.isEmpty()){
//			simplex.pollArrow();
//		}
//		
//		double[] p1 = {3,-2};
//		double[] p2 = {1,1};
//		
//		simplex.addVertex(p1);
//		simplex.addVertex(p2);
		
		
		System.out.println("\n-----------Bow------------");
		Vertex arrow = simplex.pollArrow();
	
		for (Iterator<Vertex> it = simplex.iterator(); it.hasNext();){
			Vertex y_n = it.next();
			try {
				System.out.println(y_n.getCoordinates().asString()+" "+y_n.getFitness());
			} catch (FitnessNotEvaluatedException e) {	e.printStackTrace();}
		}
		
		double[] bow = Gradients.getBowHyperplane(simplex);
		
		System.out.println("----------Coeficients Bow------------");
		for(int i =0;i<bow.length;i++){
			System.out.print(" "+bow[i]);
		}
		System.out.println();
		
		double[] rest = Gradients.getRestArrow(gradients, arrow.getCoordinates().getCoordinatesAsDouble(), bow);
		
		System.out.println("----------Rest of arrow point------------");
		for(int i =0;i<rest.length;i++){
			System.out.print(" "+rest[i]);
		}
		System.out.println();
		
		System.out.println("----------Height from Bow to Target------------");
		double heightBowToTarget = Gradients.getHeightFromBowToTarget(volumeSimplex,volumeSimplex/2.0, arrow.getCoordinates().getCoordinatesAsDouble(), bow);
		System.out.println(""+heightBowToTarget);

		
		System.out.println("----------Coeficients Target------------");
		System.out.println("---Inner Target");
		double[][] target = Gradients.getTargets(heightBowToTarget, arrow.getCoordinates().getCoordinatesAsDouble(), rest, bow);
		for(int i =0;i<target[0].length;i++){
			System.out.print(" "+target[0][i]);
		}
		System.out.println();
		System.out.println("---Outer Target");
		for(int i =0;i<target[1].length;i++){
			System.out.print(" "+target[1][i]);
		}
		System.out.println();
		
		System.out.println("----------Two points in Target------------");
		// Ax + By + C = 0 -> y = -Ax -C / B 
		// or -> x = -By -C / A
		
		System.out.println("---Inner Target");
		//hack 
		double x1 = (-target[0][1] * 0 - target[0][2]) / target[0][0];
		double yo = (-target[0][0] *  124.08064788027995 - target[0][2]) / target[0][1];
		System.out.println(x1+" "+0.0);
		System.out.println(124.08064788027995+" "+yo);

		System.out.println("---Outer Target");
		//hack 
		x1 = (-target[1][1] * 0 - target[1][2]) / target[1][0];
		yo = (-target[1][0] *  -62.04032394013998 - target[1][2]) / target[1][1];
		System.out.println(x1+" "+0.0);
		System.out.println(-62.04032394013998+" "+yo);

		System.out.println("----------aim and bullseye point------------");
		
		double[] aim = Gradients.getIntersectionOfaVectorWithHyperplane(gradients, arrow.getCoordinates().getCoordinatesAsDouble(), target[0]);
		double[] bullseye = Gradients.getIntersectionOfaVectorWithHyperplane(gradients, arrow.getCoordinates().getCoordinatesAsDouble(), target[1]);

		System.out.println("---Aim");
		for(int i =0;i<rest.length;i++){
			System.out.print(" "+aim[i]);
		}
		System.out.println();

		System.out.println("---Bullseye");
		for(int i =0;i<rest.length;i++){
			System.out.print(" "+bullseye[i]);
		}
		System.out.println();
	}
	
}


